#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define NUM_THREADS  8   //5 no args, 8 one argss, 3 pthread_cond
#define N 1000
#define MEGEXTRA 1000000
#define NTHREADS 4
#define NUMTHRDS 4
#define VECLEN 100
#define TCOUNT 10
#define COUNT_LIMIT 12

//#define NO_ARGS
#define ONE_ARGS
//#define MUL_ARGS
//#define PHTREAD_JOIN
//#define PTHREAD_SETSIZE
//#define PTHREAD_MUTEX
//#define PTHREAD_COND

#define pthread_t_struct 0
#ifdef MUL_ARGS
struct thread_data
{
    int	thread_id;
    int  sum;
    char *message;
};

struct thread_data thread_data_array[NUM_THREADS];
#endif

typedef struct
{
    double      *a;
    double      *b;
    double     sum;
    int     veclen;
} DOTDATA;

DOTDATA dotstr = {0};
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;

#ifdef PTHREAD_COND
int     count = 0;
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;
int     thread_ids[3] = {0, 1, 2};
#endif



pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
char *messages[NUM_THREADS];

void *PrintHello(void *threadid)
{
#ifdef NO_ARGS
    long tid;
    tid = (long)threadid;
    printf("Hello World! It's me, thread #%ld!\n", tid);
#endif
#ifdef ONE_ARGS
    int *id_ptr, taskid;
   // sleep(1);
    id_ptr = (int *) threadid;
    taskid = *id_ptr;
    printf("Thread %d: %s\n", taskid, messages[taskid]);
#endif
#ifdef MUL_ARGS
    int taskid, sum;
    char *hello_msg;
    struct thread_data *my_data;
    sleep(1);
    my_data = (struct thread_data *) threadid;
    taskid = my_data->thread_id;
    sum = my_data->sum;
    hello_msg = my_data->message;
    printf("Thread %d: %s  Sum=%d\n", taskid, hello_msg, sum);
#endif
    pthread_exit(NULL);
}

#ifdef PHTREAD_JOIN
void *BusyWork(void *t)
{
    int i;
    long tid;
    double result = 0.0;
    tid = (long)t;
    printf("Thread %ld starting...\n", tid);

    for (i = 0; i < 1000000; i++)
    {
        result = result + sin(i) * tan(i);
    }

    printf("Thread %ld done. Result = %e\n", tid, result);
    pthread_exit((void *) t);
}
#endif 
void *dowork(void *threadid)
{
    double A[N][N];
    int i, j;
    long tid;
    size_t mystacksize;
    tid = (long)threadid;
    pthread_attr_getstacksize(&attr, &mystacksize);
    printf("Thread %ld: stack size = %li bytes \n", tid, mystacksize);

    for (i = 0; i < N; i++)
        for (j = 0; j < N; j++)
        {
            A[i][j] = ((i * j) / 3.452) + (N - i);
        }

    pthread_exit(NULL);
}

void *dotprod(void *arg)
{
    /* Define and use local variables for convenience */
    int i, start, end, len ;
    long offset;
    double mysum, *x, *y;
    offset = (long)arg;
    len = dotstr.veclen;
    start = offset * len;
    end   = start + len;
    x = dotstr.a;
    y = dotstr.b;
    /*
    Perform the dot product and assign result
    to the appropriate variable in the structure.
    */
    mysum = 0;

    for (i = start; i < end ; i++)
    {
        mysum += (x[i] * y[i]);
    }

    /*
    Lock a mutex prior to updating the value in the shared
    structure, and unlock it upon updating.
    */
    pthread_mutex_lock(&mutexsum);
    dotstr.sum += mysum;
    pthread_mutex_unlock(&mutexsum);
    pthread_exit((void *) 0);
}
#ifdef PTHREAD_COND
void *inc_count(void *t)
{
    int i;
    long my_id = (long)t;

    for (i = 0; i < TCOUNT; i++)
    {
        pthread_mutex_lock(&count_mutex);
        count++;

        /*
        Check the value of count and signal waiting thread when condition is
        reached.  Note that this occurs while mutex is locked.
        */
        if (count == COUNT_LIMIT)
        {
            pthread_cond_signal(&count_threshold_cv);
            printf("inc_count(): thread %ld, count = %d  Threshold reached.\n",
                   my_id, count);
        }

        printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",
               my_id, count);
        pthread_mutex_unlock(&count_mutex);
        /* Do some "work" so threads can alternate on mutex lock */
        sleep(1);
    }

    pthread_exit(NULL);
}

void *watch_count(void *t)
{
    long my_id = (long)t;
    printf("Starting watch_count(): thread %ld\n", my_id);
    /*
    Lock mutex and wait for signal.  Note that the pthread_cond_wait
    routine will automatically and atomically unlock mutex while it waits.
    Also, note that if COUNT_LIMIT is reached before this routine is run by
    the waiting thread, the loop will be skipped to prevent pthread_cond_wait
    from never returning.
    */
    pthread_mutex_lock(&count_mutex);

    while (count < COUNT_LIMIT)
    {
        pthread_cond_wait(&count_threshold_cv, &count_mutex);
        printf("watch_count(): thread %ld Condition signal received.\n", my_id);
        count += 125;
        printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
    }

    pthread_mutex_unlock(&count_mutex);
    pthread_exit(NULL);
}
#endif
int main(int argc, char *argv[])
{
#ifdef NO_ARGS
    int rc;
    long t;

    for (t = 0; t < NUM_THREADS; t++)
    {
        printf("In main: creating thread %ld\n", t);
        rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);

        if (rc)
        {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
        pthread_detach(pthread_self(void));
    }

#endif
#ifdef ONE_ARGS
    int *taskids[NUM_THREADS];
    int rc, t;
    messages[0] = "English: Hello World!";
    messages[1] = "French: Bonjour, le monde!";
    messages[2] = "Spanish: Hola al mundo";
    messages[3] = "Klingon: Nuq neH!";
    messages[4] = "German: Guten Tag, Welt!";
    messages[5] = "Russian: Zdravstvytye, mir!";
    messages[6] = "Japan: Sekai e konnichiwa!";
    messages[7] = "Latin: Orbis, te saluto!";

    for (t = 0; t < NUM_THREADS; t++)
    {
        taskids[t] = (int *) malloc(sizeof(int));
        *taskids[t] = t;
        printf("Creating thread %d\n", t);

        #if pthread_t_struct
        rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskids[t]);

        if (rc)
        {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
        #else
        pthread_t *pid=(pthread_t *)malloc(sizeof(pthread_t));
        rc = pthread_create(pid, NULL, PrintHello, (void *) taskids[t]);

        if (rc)
        {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
        pthread_detach(*pid);
        #endif
    }

#endif
#ifdef MUL_ARGS
    int *taskids[NUM_THREADS];
    int rc, t, sum;
    sum = 0;
    messages[0] = "English: Hello World!";
    messages[1] = "French: Bonjour, le monde!";
    messages[2] = "Spanish: Hola al mundo";
    messages[3] = "Klingon: Nuq neH!";
    messages[4] = "German: Guten Tag, Welt!";
    messages[5] = "Russian: Zdravstvytye, mir!";
    messages[6] = "Japan: Sekai e konnichiwa!";
    messages[7] = "Latin: Orbis, te saluto!";

    for (t = 0; t < NUM_THREADS; t++)
    {
        sum = sum + t;
        thread_data_array[t].thread_id = t;
        thread_data_array[t].sum = sum;
        thread_data_array[t].message = messages[t];
        printf("Creating thread %d\n", t);
        rc = pthread_create(&threads[t], NULL, PrintHello, (void *)
                            &thread_data_array[t]);

        if (rc)
        {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

#endif
#ifdef PHTREAD_JOIN
    int rc;
    long t;
    void *status;
    /* Initialize and set thread detached attribute */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for (t = 0; t < NUM_THREADS; t++)
    {
        printf("Main: creating thread %ld\n", t);
        rc = pthread_create(&threads[t], &attr, BusyWork, (void *)t);

        if (rc)
        {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    /* Free attribute and wait for the other threads */
    pthread_attr_destroy(&attr);

    for (t = 0; t < NUM_THREADS; t++)
    {
        rc = pthread_join(threads[t], &status);

        if (rc)
        {
            printf("ERROR; return code from pthread_join() is %d\n", rc);
            exit(-1);
        }

        printf("Main: completed join with thread %ld having a status of %ld\n", t, (long)status);
    }

    printf("Main: program completed. Exiting.\n");
#endif
#ifdef PTHREAD_SETSIZE
    size_t stacksize;
    int rc;
    long t;
    pthread_attr_init(&attr);
    pthread_attr_getstacksize(&attr, &stacksize);
    printf("Default stack size = %li\n", stacksize);
    stacksize = sizeof(double) * N * N + MEGEXTRA;
    printf("Amount of stack needed per thread = %li\n", stacksize);
    pthread_attr_setstacksize(&attr, stacksize);
    printf("Creating threads with stack size = %li bytes\n", stacksize);

    for (t = 0; t < NTHREADS; t++)
    {
        rc = pthread_create(&threads[t], &attr, dowork, (void *)t);

        if (rc)
        {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    printf("Created %ld threads.\n", t);
#endif
#ifdef PTHREAD_MUTEX
    long i;
    double *a, *b;
    void *status;
    /* Assign storage and initialize values */
    a = (double *) malloc(NUMTHRDS * VECLEN * sizeof(double));
    b = (double *) malloc(NUMTHRDS * VECLEN * sizeof(double));

    for (i = 0; i < VECLEN * NUMTHRDS; i++)
    {
        a[i] = 1.0;
        b[i] = a[i];
    }

    dotstr.veclen = VECLEN;
    dotstr.a = a;
    dotstr.b = b;
    dotstr.sum = 0;
    pthread_mutex_init(&mutexsum, NULL);
    /* Create threads to perform the dotproduct  */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for (i = 0; i < NUMTHRDS; i++)
    {
        /*
        Each thread works on a different set of data.
        The offset is specified by 'i'. The size of
        the data for each thread is indicated by VECLEN.
        */
        pthread_create(&callThd[i], &attr, dotprod, (void *)i);
    }

    pthread_attr_destroy(&attr);

    /* Wait on the other threads */
    for (i = 0; i < NUMTHRDS; i++)
    {
        pthread_join(callThd[i], &status);
    }

    /* After joining, print out the results and cleanup */
    printf("Sum =  %f \n", dotstr.sum);
    free(a);
    free(b);
    pthread_mutex_destroy(&mutexsum);
#endif
#ifdef PTHREAD_COND
    int i, rc;
    long t1 = 1, t2 = 2, t3 = 3;
    pthread_t threads[3];
    pthread_attr_t attr;
    /* Initialize mutex and condition variable objects */
    pthread_mutex_init(&count_mutex, NULL);
    pthread_cond_init(&count_threshold_cv, NULL);
    /* For portability, explicitly create threads in a joinable state */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&threads[0], &attr, watch_count, (void *)t1);
    pthread_create(&threads[1], &attr, inc_count, (void *)t2);
    pthread_create(&threads[2], &attr, inc_count, (void *)t3);

    /* Wait for all threads to complete */
    for (i = 0; i < NUM_THREADS; i++)
    {
        pthread_join(threads[i], NULL);
    }

    printf("Main(): Waited on %d  threads. Done.\n", NUM_THREADS);
    /* Clean up and exit */
    pthread_attr_destroy(&attr);
    pthread_cond_destroy(&count_threshold_cv);
    pthread_mutex_destroy(&count_mutex);

#endif
    //pthread_exit(NULL);
}
